/******************************************************************************
 * Copyright 2018 The Apollo Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/
#include "config_manager.h"
#include "utils/common/perception_gflags.h"
#include "utils/common/environment.h"
#include "utils/common/file.h"
#include "utils/common/io/io_util.h"


namespace lib {

using common::GetAbsolutePath;
using common::GetProtoFromASCIIFile;

ConfigManager::ConfigManager() {
    work_root_ = FLAGS_work_root;

    // For start at arbitrary path
    if (work_root_.empty()) {
        work_root_ = common::GetEnv("MODULE_PATH");
        if (work_root_.empty()) {
            work_root_ = common::GetEnv("CYBER_PATH");
        }
    }
}

bool ConfigManager::Init() {
    MutexLock lock(&mutex_);
    return InitInternal();
}

bool ConfigManager::InitInternal() {
    if (inited_) {
        return true;
    }
    for (auto iter = model_config_map_.begin(); iter != model_config_map_.end();
         ++iter) {
        delete iter->second;
    }
    model_config_map_.clear();
    // std::string config_module_path =
    //     GetAbsolutePath(work_root_, FLAGS_config_manager_path);
    std::string config_module_path = work_root_ + FLAGS_config_manager_path;
    std::cout << "WORK_ROOT: " << work_root_
              << " config_root_path: " << config_module_path << std::endl;

    std::vector<std::string> model_config_files;
    if (!common::GetFileList(config_module_path, "config_manager.config",
                             &model_config_files)) {
        std::cout << "ERROR: "
                  << "config_root_path : " << config_module_path
                  << " get file list error."<< std::endl;
        return false;
    }

    for (const auto& model_config_file : model_config_files) {
        fusion::ModelConfigFileListProto file_list_proto;
        if (!GetProtoFromASCIIFile(model_config_file, &file_list_proto)) {
            std::cout << "ERROR: "
                      << "Invalid ModelConfigFileListProto file: " << model_config_file<< std::endl;
            return false;
        }

        for (const std::string& model_config_path :
             file_list_proto.model_config_path()) {
            const std::string abs_path =
                GetAbsolutePath(work_root_, model_config_path);
            fusion::MultiModelConfigProto multi_model_config_proto;
            if (!GetProtoFromASCIIFile(abs_path, &multi_model_config_proto)) {
                std::cout << "ERROR: "
                          << "Invalid MultiModelConfigProto file: " << abs_path<< std::endl;
                return false;
            }

            for (const fusion::ModelConfigProto& model_config_proto :
                 multi_model_config_proto.model_configs()) {
                ModelConfig* model_config = new ModelConfig();
                if (!model_config->Reset(model_config_proto)) {
                    return false;
                }

                std::cout << "load ModelConfig succ. name: " << model_config->name()<< std::endl;

                auto result =
                    model_config_map_.emplace(model_config->name(), model_config);
                if (!result.second) {
                    std::cout << "WARN:  " << "duplicate ModelConfig, name: " << model_config->name()<< std::endl;
                    return false;
                }
            }
        }
    }

    std::cout << "finish to load ModelConfigs. NumModels: "
              << model_config_map_.size()<< std::endl;

    inited_ = true;

    return true;
}

bool ConfigManager::Reset() {
    MutexLock lock(&mutex_);
    inited_ = false;
    return InitInternal();
}

bool ConfigManager::GetModelConfig(const std::string& model_name,
                                   const ModelConfig** model_config) {
    if (!inited_ && !Init()) {
        return false;
    }

    auto citer = model_config_map_.find(model_name);
    if (citer == model_config_map_.end()) {
        return false;
    }
    *model_config = citer->second;
    return true;
}

ConfigManager::~ConfigManager() {
    for (auto iter = model_config_map_.begin(); iter != model_config_map_.end();
         ++iter) {
        delete iter->second;
    }
}

bool ModelConfig::Reset(const fusion::ModelConfigProto& proto) {
    name_ = proto.name();
    version_ = proto.version();

    integer_param_map_.clear();
    string_param_map_.clear();
    double_param_map_.clear();
    float_param_map_.clear();
    bool_param_map_.clear();
    array_integer_param_map_.clear();
    array_string_param_map_.clear();
    array_double_param_map_.clear();
    array_float_param_map_.clear();
    array_bool_param_map_.clear();

    for (const fusion::KeyValueInt& pair : proto.integer_params()) {
        integer_param_map_.emplace(pair.name(), pair.value());
    }

    for (const fusion::KeyValueString& pair : proto.string_params()) {
        string_param_map_.emplace(pair.name(), pair.value());
    }

    for (const fusion::KeyValueDouble& pair : proto.double_params()) {
        double_param_map_.emplace(pair.name(), pair.value());
    }

    for (const fusion::KeyValueFloat& pair : proto.float_params()) {
        float_param_map_.emplace(pair.name(), pair.value());
    }

    for (const fusion::KeyValueBool& pair : proto.bool_params()) {
        bool_param_map_.emplace(pair.name(), pair.value());
    }

    for (const fusion::KeyValueArrayInt& pair : proto.array_integer_params()) {
        std::vector<int> values;
        RepeatedToVector(pair.values(), &values);
        array_integer_param_map_.emplace(pair.name(), values);
    }

    for (const fusion::KeyValueArrayString& pair : proto.array_string_params()) {
        std::vector<std::string> values;
        values.reserve(pair.values_size());
        for (const std::string& value : pair.values()) {
            values.push_back(value);
        }
        array_string_param_map_.emplace(pair.name(), values);
    }

    for (const fusion::KeyValueArrayDouble& pair : proto.array_double_params()) {
        std::vector<double> values;
        RepeatedToVector(pair.values(), &values);
        array_double_param_map_.emplace(pair.name(), values);
    }

    for (const fusion::KeyValueArrayFloat& pair : proto.array_float_params()) {
        std::vector<float> values;
        RepeatedToVector(pair.values(), &values);
        array_float_param_map_.emplace(pair.name(), values);
    }

    for (const fusion::KeyValueArrayBool& pair : proto.array_bool_params()) {
        std::vector<bool> values;
        RepeatedToVector(pair.values(), &values);
        array_bool_param_map_.emplace(pair.name(), values);
    }

    std::cout << "reset ModelConfig. model_name: " << name_
              << " integer_param_map's size: " << integer_param_map_.size()
              << " string_param_map's size: " << string_param_map_.size()
              << " double_param_map's size: " << double_param_map_.size()
              << " float_param_map's size: " << float_param_map_.size()
              << " bool_param_map's size: " << bool_param_map_.size()
              << " array_integer_param_map's size: "
              << array_integer_param_map_.size()
              << " array_string_param_map's size: " << array_string_param_map_.size()
              << " array_double_param_map's size: " << array_double_param_map_.size()
              << " array_float_param_map's size: " << array_float_param_map_.size()
              << " array_bool_param_map's size: " << array_bool_param_map_.size()
              << std::endl;

    return true;
}

}  // namespace lib
